<?php

/**
 * This class presents pages for the plug-in.
 *
 */
class folio_page {

    // Public vars in DB
    public $page_ident = -1;
    public $user_ident = -1;
    public $creator_ident = -1;
    public $created;
    public $title = '';
    public $newest = 1;
    public $parentpage_ident = -1;
    public $format_ident = 0;
    public $body = '';
    public $security_ident = -1;
    public $attachments = '';
    public $help = '';

    // Constructor
    function folio_page( $array = false ) {
        // Assume constructor is an array from the db.

        if ( !($array == false ) ) {
            // Load values from array.
            foreach ( $array as $n=>$v ) {
                $this->$n = $v; //( $r . ' = ' . $v . '<br/>' );
            }
        }
    }

    /**
     * Use to return a new page, properly initialized and already saved to the db.
     * Defaults security to the default level.
     *
     * @param folio_user $user User to create the new page for.
     * @param string $title Name of the new page
     * @param string $body Body of the new page
     * @param int $format_ident
     * @param int $parentpage_ident
     * @return folio_page
     */
    public function NewPageFactory($user, $title, $body, $format_ident, $parentpage_ident ) {
        global $FOLIO_CFG;
        $page = new folio_page();

        // Go ahead and initialize vars
        $page->page_ident = folio_page::selectNextPageIdent();
        $page->creator_ident = intval($_SESSION['userid']);
        $page->user_ident = intval( $user->ident );
        $page->title = $title;
        $page->security_ident = $page->page_ident;
        $page->format_ident = $format_ident;
        $page->body = $body;
        $page->parentpage_ident = $parentpage_ident;

        // Save resulting page in the db.
        $page->Update();

        // Set permission to default.
        //        Needs to be called after the page is already loaded into the db.
        folio_page_permission::Update($page, $FOLIO_CFG->page_defaultpermission);

        plugin_hook("folio_page","inserted", $page);
        
        return $page;
    }

    /**
     * Tests to see if this is a homepage.
     *
     * @return boolean
     */
    public function IsHomepage() {
        return $this->page_ident == $this->parentpage_ident;
    }

    /**
     * Test security for the page.
     *
     * @param string $type Update or Select Permission
     */
    public function Permission( $type ) {
        global $CFG;

        if ( isloggedin() ) {
            $profile_id = $_SESSION['userid'];
        } else {
            $profile_id = -1;
        }

        // Get permission records.
        // Create a fake first column to satisfy ADODB's requirement of a unique first field.
        $permissions =     recordset_to_array(
            get_recordset_sql( "SELECT CONCAT(user_ident, '.', security_ident) jointkey, " .
            'user_ident, security_ident, accesslevel FROM ' . $CFG->prefix .
            'folio_page_security WHERE ' .
            "security_ident = {$this->page_ident} ORDER BY user_ident" ) );


        if( $type == 'update' ) {
            return folio_page::folio_page_permission($this, $permissions, 'write', $profile_id);
        } elseif ( $type == 'select' ) {
            return folio_page::folio_page_permission($this, $permissions, 'read', $profile_id);
        } else {
            trigger_error('Invalid type: ' . $type . ' passed to folio_page::Permission', E_USER_ERROR);
        }


    }

    /**
     * Test security for creating a new page for a certain user.
     * This is a static function, as we don't have a page to access $this yet.
     *
     * @param folio_user $user
     * @return boolean
     */
    public function InsertPermission( $user ) {

        // Only allow logged in users to create pages.
        if ( !isloggedin() ) {
            return false;
        }

        // Simulate an empty set of permission records.
        $permissions = array();

        return folio_page::folio_page_permission( false, $permissions, 'write', $user->ident );
    }

/**
* Used to see the current user has permission to something.
*   May pull up community records to see if they have membership rights thru that.
*
* @uses $_SESSION[userid]
* @param array $page The page record being checked.  Should be from folio_page_select.  False if we're creating a new page.
* @param array $permissions An array of mysql records of permissions for the current page
* @param string $access The type of permission that we're asking about.  Valid options = read/write
* @param int OPTIONAL $opt_newpageowner An optional page location variable used for signaling where new pages will be located.
* @return bit Whether or not the given user has access.
**/
private function folio_page_permission( $page, $permissions, $access, $opt_newpageowner = '') {
    global $CFG;
    global $_SESSION;

    // Test for non-logged in users
    if ( !isloggedin() ) {

        // See if there is a public permission level for write (or moderated for reader)
        if ( !$page & $access == 'write' ) {
            // Don't allow non-logged in users to create a new page.
            return false;
        } elseif ( $access == 'read' ) {
            if ( !!$permissions ) {
                foreach ($permissions as $permission) {
                    if ( $permission->accesslevel == 'PUBLIC' |
                        $permission->accesslevel == 'MODERATED' ) {
                        return true;
                    }
                }
            }
        } elseif ( $access == 'write' ) {
            if ( !!$permissions ) {
            foreach ($permissions as $permission) {
                if ( $permission->accesslevel == 'PUBLIC' ) {
                    return true;
                }
            }
            }
        }

        // Not found anything.  Return false - non-logged in user doesn't have access rights.
        return false;
    }

    // Continue, knowing that the user is logged in and we have a userid

    // I had problems directly accessing $_SESSION[userid] and found that
    //        assigning it to a variable solved my boolean evaluation problems
    //        with it.
    $userid = array( $_SESSION['userid'] );

    // See if we're adding a record to our own site.
    //      Page can be false if we're adding a new page. First condition avoids a notice being throw.
    if ( $page !== false AND intval( $_SESSION['userid']) == intval($page->user_ident )) {
        return true;
    }

    // Grab community IDs for friends.
    $groupid = recordset_to_array(
        get_recordset_sql( "SELECT * FROM " . $CFG->prefix . "friends f " .
            " INNER JOIN " . $CFG->prefix . "users u on f.friend = u.ident " .
            "WHERE f.owner = " . $_SESSION['userid'] . " AND u.user_type='community' ") );

    if ( !!$groupid ) {
        foreach ( $groupid as $id ) {
            $userid[] = $id->friend;
        }
    }

    // Grab community IDs for owned communities
    $groupid = recordset_to_array(
        get_recordset_sql( "SELECT * FROM " . $CFG->prefix . "users ".
            "WHERE user_type = 'community' AND owner = " . $_SESSION['userid'] ) );

    if ( !!$groupid ) {
        foreach ( $groupid as $id ) {
            $userid[] = $id->ident;
        }
    }

    // Look to see if the user has permission
    if ( !$page ) {
        // New page.  See if it in located in a place where the user has permission to access it.
        return ( in_array( $opt_newpageowner, $userid ) );
    } elseif ( $access == 'read' ) {
        // Page exists, see if we can read it.
        foreach ($permissions as $permission) {
            if ( $permission->accesslevel == 'PUBLIC' OR
                $permission->accesslevel == 'MODERATED' OR
                ( in_array( $page->user_ident, $userid)
                )
                ) {
                return true;
            }
        }
    } elseif ( $access == 'write' ) {
        // Page exists, see if we can edit it.
        foreach ($permissions as $permission) {
            if ( $permission->accesslevel == 'PUBLIC' OR
                ( in_array( $page->user_ident, $userid) )
                ) {
                return true;
            }
        }

    } elseif ( $access == 'delete' ) {
        // See if the user is a member / owner.
        return ( in_array( $page->user_ident, $userid ) );

    } else {
        error( 'Invalid ' . $access . ' passed to folio_page_permission');
    }
    // Didn't catcth anywhere, say no access.
    return false;
}

    /**
    * Use to encode string to be in a url.
    **/
    public function title_encode( $title ) {
        // Note that I manually pull out periods, as the url
        //  encode function won't.  Also change % to ' as the
        //  % has problems with url htaccess rules in apache.
        return str_replace( '%',"'",
                    str_replace('.', '%2E',
                        folio_page::utf16_urlencode( $title )));
    }

    /**
     * Decode a string encoded by title_encode to its original value
     */
    public function title_decode( $title ) {
        return urldecode( str_replace("'","%", $title));
    }

    // Should relatively safe on unicode.
    // Source; http://us.php.net/urlencode
    //  simon dot rupf at int-ag dot ch
    //  12 Apr 2008 6:20
    private function utf16_urlencode ( $str ) {
        # convert characters > 255 into HTML entities
        $convmap = array( 0xFF, 0x2FFFF, 0, 0xFFFF );
        $str = mb_encode_numericentity( $str, $convmap, "UTF-8");

        # escape HTML entities, so they are not urlencoded
        $str = preg_replace( '/&#([0-9a-fA-F]{2,5});/i', 'mark\\1mark', $str );
        $str = urlencode($str);

        # now convert escaped entities into unicode url syntax
        $str = preg_replace( '/mark([0-9a-fA-F]{2,5})mark/i', '%u\\1', $str );
        return $str;
    }
//////////////////
//              //
//  SQL Layer   //
//              //
//////////////////

    /**
     * Return the homepage for the passed user.  False if not created.
     *
     * @param folio_user $user
     * @return folio_page
     */
    public function SelectHomepage( folio_user $user ) {
        global $CFG;

        $pages = recordset_to_array(
        get_recordset_sql('SELECT p.*, u.username FROM ' . $CFG->prefix . 'folio_page p inner join ' .
            $CFG->prefix . 'users u on p.user_ident = u.ident ' .
            "WHERE p.page_ident = p.parentpage_ident AND u.username = '$user->username' AND newest = 1 LIMIT 1")
        );

        if ( !$pages ) {
            return false;
        } else {
            foreach ($pages as $page)
                return new  folio_page($page);
        }
    }

   /**
    * Return the page for the passed ident.  False if not created.
    *
    * @param int $page_ident
    * @return folio_page
    */
    public function SelectWherePageIdent( $page_ident, $created = -1 ) {
        global $CFG;

        if ($created != -1) {
            // Get old page from archive
            $pages = recordset_to_array(
            get_recordset_sql('SELECT * FROM ' . $CFG->prefix . 'folio_page_archive ' .
                "WHERE page_ident = $page_ident AND created = $created LIMIT 1")
            );
        } else {
            // Get current page
            $pages = recordset_to_array(
                get_recordset_sql('SELECT * FROM ' . $CFG->prefix . 'folio_page ' .
                    "WHERE page_ident = $page_ident AND newest = 1 LIMIT 1")
                );
        }

        if ( $pages == false OR count( $pages ) == 0 ) {
            return false;
        } else {
            return new folio_page( $pages[$page_ident] );
        }
    }


   /**
     * Return all versions of the current page.
     *
     * @return array of folio_page
     */
    public function SelectHistory( ) {
        global $CFG;

        $p = array();

        $pages = recordset_to_array(
            get_recordset_sql(
            "SELECT created, page_ident, user_ident, creator_ident, title, " .
            "newest, parentpage_ident, format_ident, body, security_ident FROM {$CFG->prefix}folio_page_archive " .
            "WHERE page_ident = {$this->page_ident} ORDER BY created DESC"
            ) );

        // Convert returned rows into folio_page records.
        if ( !!$pages ) {
            foreach( $pages as $page ) {
                $p[$page->created] = new folio_page($page);
            }
        }
        return $p;
    }

   /**
     * Return the children of the passed page.
     *
     * @param int $page_ident
     * @return array of folio_pages
     */
    public function SelectWhereParentpageIdent( $page_ident ) {
        global $CFG;

        $pages = recordset_to_array(
        get_recordset_sql("SELECT * FROM " . $CFG->prefix . "folio_page " .
            'WHERE parentpage_ident = ' . intval($page_ident) . ' AND page_ident <> ' . intval($page_ident) .
            ' AND newest = 1 ORDER BY title ')
        );

        if ( !$pages ) {
            // No results.
            return array();
        } else {
            // Results
            foreach( $pages as $key=>$page ) {
                $pages[$key] = new folio_page( $page );
            }
            return $pages;
        }
    }

    /**
     * Find the page matcing the passed title & username.
     *
     * @param string $title
     * @param string $username
     * @return folio_page
     */
    public function SelectWhereTitleAndUsername( $title, $username ) {
        global $CFG;

        $pages = recordset_to_array( get_recordset_sql( "SELECT page_ident, p.* FROM {$CFG->prefix}folio_page p " .
            "INNER JOIN {$CFG->prefix}users u ON p.user_ident = u.ident " .
            "WHERE LOWER(p.title) = ? AND p.newest = 1 AND LOWER(u.username) = ? " .
            ' LIMIT 1 ',
            array( $title, $username ) ) );
        $page = false;

        if ( $pages ) {
            foreach ($pages as $p) {
                // Return page ident.
                $page = new folio_page( $p );
            }
        }
        return $page;
    }

    /**
     * Update the page in the db.
     * Automatically sets the creator and created fields
     *
     * @param folio_page $page
     * @return folio_page
     */
    public function Update() {
        global $CFG;
        //    Note that calling this function with an old page, when the page has
        //        already been updated to the db, but not had the created property
        //        updated, will result in a key failure.  That is why it's essential
        //        that $this be used instead of having $page as a param.

        // Update time is crucial, as it's half of the Primary Key for the table.
        //    If the existing created == time(), then Update has been called 2x
        //    during script execution.  Delete the last record and insert the new one.
        if ( $this->created == time() ) {
            // Delete last record.
            //         Don't use the this->Delete method, as we can't delete any associated
            //        artifacts and decorations.
            delete_records( 'folio_page',
                'page_ident', $this->page_ident, 'created', $this->created );
            delete_records( 'folio_page_archive',
                'page_ident', $this->page_ident, 'created', $this->created );

        } else {
            // Set last record to !newest

            // Modify old record by converting any records with matching page_ident and
            //      newest = true to newest = false
            set_field('folio_page_archive', 'newest', 0 , 'newest', 1, 'page_ident', $this->page_ident);
        }

        // Save this record.
        $this->created = time();
        $this->title = trim( $this->title);
        $this->creator_ident = $_SESSION['userid'];

        // Create a blank record for inserting/updating.
        $i = new StdClass();
        foreach( $this as $n=>$v ){
            $i->$n=$v;
        }

        // Insert new record into db archive.
        insert_record('folio_page_archive',$i, true, 'page_ident');
        
        // Look to see if the record exists in the current page table.
        $pages = recordset_to_array(
            get_recordset_sql('SELECT * FROM ' . $CFG->prefix . 'folio_page ' .
                "WHERE page_ident = $this->page_ident AND newest = 1 LIMIT 1")
                );
        
        if( $pages == false OR count( $pages ) == 0 )
        {
            // Insert into current page table
            insert_record('folio_page',$i, true, 'page_ident');
        } else
        {   // Update current page.
            self::local_update_record( 'folio_page', $i, 'page_ident' );
        }
        
        plugin_hook("folio_page","updated", $this);
        return $this;
    }

    /**
     * Deletes the page in the db.
     * Removes all children pages as well.
     * Works by setting new=false to allow for some degree of undo. Doesn't
     *      delete any artifacts or decorations.
     *
     * @return boolean
     */
    public function Delete() {

        // Find children and recurse.
        $children = folio_page::SelectWhereParentpageIdent($this->page_ident);

        if( count($children) > 0 ) {
            foreach($children as $child){
                $child->Delete();
            }
        }

        // Delete tags.
        folio_tags::Update( $this, "");

        // Modify old record by converting any records with matching page_ident and
        //      newest = true to newest = false
        set_field('folio_page', 'newest', 0, 'newest', 1, 'page_ident', $this->page_ident);
        set_field('folio_page_archive', 'newest', 0, 'newest', 1, 'page_ident', $this->page_ident);

        plugin_hook("folio_page","deleted", $this);
        
        return true;
    }


    /**
     * Called to get the next needed page_ident numbers.
     *      Works by inserting a row into a table, getting the next ident,
     *     and then retrieving.
     *    Checks to make sure that it's a unique number.  If not, then
     *     goes again.
     *
     * @return int New Page Ident
     **/
    public function selectNextPageIdent() {
        global $CFG;
        $page_ident = false;

        while ( $page_ident == false) {
        // Create a new record with the correct vars

            $page_ident = insert_record( 'folio_page_ident_generator', new StdClass() );
            $page = folio_page::SelectWherePageIdent( $page_ident );
            if( $page !== false ) {
                $page_ident = false;
            }
        }

        return $page_ident;
    }

    /**
     * Called to return all pages for the passed user_ident
     * @param int $user
     * @return array
     */
    function SelectWhereUserIdent( $user_ident ) {
        global $CFG;

        $pages = recordset_to_array( get_recordset_sql( "SELECT page_ident, p.* FROM {$CFG->prefix}folio_page p " .
            "WHERE p.user_ident = " . intval( $user_ident ) . " AND p.newest = 1"));

        $p = array();

        if ( !!$pages ) {
            foreach ($pages as $page) {
                // Turn into a regular page record.
                $p[$page->page_ident] = new folio_page( $page );
            }
        }
        return $p;
    }

    
    // This is a modification of update_record in datalib.php that is
    //      able to accept alternative ident column names.
    private function local_update_record($table, $dataobject, $ident) {
        global $db, $CFG;

        static $table_columns;

        // Determine all the fields in the table
        if (is_array($table_columns) && array_key_exists($table,$table_columns)) {
            $columns = $table_columns[$table];
        } else {
            if (!$columns = $db->MetaColumns($CFG->prefix . $table)) {
                return false;
            }
            $table_columns[$table] = $columns;
        }

        $data = (array)$dataobject;
        $ddd = array();

        if (defined('ELGG_PERFDB')) { global $PERF ; $PERF->dbqueries++; };

        // Pull out data matching these fields
        foreach ($columns as $column) {
            if ($column->name <> $ident and isset($data[$column->name]) ) {
                $ddd[$column->name] = $data[$column->name];
            }
        }

        // Construct SQL queries
        $numddd = count($ddd);
        $count = 0;
        $update = '';

        foreach ($ddd as $key => $value) {
            $count++;
            $update .= $key .' = ?';
            if ($count < $numddd) {
                $update .= ', ';
            }
        }

//return( 'UPDATE '. $CFG->prefix . $table .' SET '. $update .' WHERE ' . $ident . ' = \''. $dataobject->$ident .'\'' );
        $stmt = $db->Prepare('UPDATE '. $CFG->prefix . $table .' SET '. $update .' WHERE ' . $ident . ' = \''. $dataobject->$ident .'\'');
        if ($rs = $db->Execute($stmt,$ddd)) {
            elggcache_delete($table, "{$ident}_" . $dataobject->$ident);
            return true;
        } else {
            if (isset($CFG->debug) and $CFG->debug > 7) {
                notify($db->ErrorMsg() .'<br /><br />UPDATE '. $CFG->prefix . $table .' SET '. $update .' WHERE ' . $ident . ' = \''. $dataobject->$ident .'\'');
            }
            if (!empty($CFG->dblogerror)) {
                $debug = debug_backtrace();
                foreach ($debug as $d) {
                    if (strpos($d['file'],'datalib') === false) {
                        error_log("SQL ".$db->ErrorMsg()." in {$d['file']} on line {$d['line']}. STATEMENT:  UPDATE $CFG->prefix $table SET $update WHERE $ident = '{$dataobject->$ident}'");
                        break;
                    }
                }
            }
            return false;
        }
    }
    
}

?>
